﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Management;
using System.Reflection;
using WbemScripting;

namespace MissingLinq.Linq2Management.Context
{
	/// <summary>
	/// Enumerable that converts query results into the target data format
	/// </summary>
	/// <typeparam name="T"></typeparam>
	internal class ObjectReader<T> : IEnumerable<T> where T : class
	{
		internal ObjectReader(ManagementObjectCollection data, bool forUpdate, ConnectionInfo connectionInfo)
		{
			_data = data;
			_forUpdate = forUpdate;
			_connectionInfo = connectionInfo;
		}

		public IEnumerator<T> GetEnumerator()
		{
			return new Enumerator(_data, _forUpdate, _connectionInfo);
		}

		IEnumerator IEnumerable.GetEnumerator()
		{
			return GetEnumerator();
		}

		private class Enumerator : IEnumerator<T>
		{
			internal Enumerator(ManagementObjectCollection data, bool forUpdate, ConnectionInfo connectionInfo)
			{
				_dataEnumerator = data.GetEnumerator();
				_forUpdate = forUpdate;
				_connectionInfo = connectionInfo;
			}

			public T Current
			{
				get { return _current; }
			}

			object IEnumerator.Current
			{
				get { return _current; }
			}

			public bool MoveNext()
			{
				if (_dataEnumerator.MoveNext())
				{
					var managementObject = (ManagementObject)_dataEnumerator.Current;
					var instance = (T)Activator.CreateInstance(typeof(T), (_forUpdate ? managementObject : null), _connectionInfo);

					var properties = typeof(T).GetProperties();
					foreach (var property in properties)
					{
						var value = managementObject[property.Name];
						if (value != null)
						{
							if (property.PropertyType == typeof(DateTime?))
							{
								_sWbemDateTime.Value = value as String;
								// convert to UTC time (offset zero)
								property.SetValue(instance, new DateTime(_sWbemDateTime.GetVarDate(false).Ticks, DateTimeKind.Utc), null);
							}
							else
								if (property.PropertyType.IsEnum)
								{
									// no direct conversion support in Convert.ChangeType for enumerations...must use Enum.ToObject
									var convertMethod = typeof(Enum).GetMethods(BindingFlags.Static | BindingFlags.Public).First(m => m.Name == "ToObject" && m.GetParameters()[1].ParameterType == value.GetType());
									property.SetValue(instance, convertMethod.Invoke(null, new[] { property.PropertyType, value }), null);
								}
								else
								{
									property.SetValue(instance, Convert.ChangeType(value, property.PropertyType), null);
								}
						}
					}

					_current = instance;
					return true;
				}

				return false;
			}

			public void Reset()
			{
				throw new NotSupportedException();
			}

			public void Dispose()
			{
				_dataEnumerator.Dispose();
			}

			private T _current;
			private readonly bool _forUpdate;
			private readonly ConnectionInfo _connectionInfo;
			private readonly SWbemDateTime _sWbemDateTime = new SWbemDateTime();
			private readonly ManagementObjectCollection.ManagementObjectEnumerator _dataEnumerator;
		}

		private readonly bool _forUpdate;
		private readonly ConnectionInfo _connectionInfo;
		private readonly ManagementObjectCollection _data;
	}
}

